﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace CommonEngine.STSDB.Data
{
    public class DataTransformer<T> : ITransformer<T, IData>
    {
        public readonly Func<T, IData> to;
        public readonly Func<IData, T> from;

        public readonly Type Type1;
        public readonly Type Type2;
        public readonly Func<Type, MemberInfo, int> MembersOrder1;
        public readonly Func<Type, MemberInfo, int> MembersOrder2;

        public DataTransformer(Type type2, Func<Type, MemberInfo, int> membersOrder1 = null, Func<Type, MemberInfo, int> membersOrder2 = null)
        {
            if (!TransformerHelper.CheckCompatible(typeof(T), type2, new HashSet<Type>(), membersOrder1, membersOrder2))
                throw new ArgumentException(String.Format("Type {0} is not compatible with {1}", typeof(T), type2));

            Type1 = typeof(T);
            Type2 = type2;
            MembersOrder1 = membersOrder1;
            MembersOrder2 = membersOrder2;

            to = CreateToMethod().Compile();
            from = CreateFromMethod().Compile();
        }

        public Expression<Func<T, IData>> CreateToMethod()
        {
            var value = Expression.Parameter(Type1);
            var data = Expression.Variable(typeof(Data<>).MakeGenericType(Type2));

            List<Expression> list = new List<Expression>();
            if (TransformerHelper.IsEqualsTypes(Type1, Type2))
                list.Add(Expression.Label(Expression.Label(typeof(IData)), Expression.New(data.Type.GetConstructor(new Type[] { Type1 }), value)));
            else
            {
                list.Add(Expression.Assign(data, Expression.New(data.Type.GetConstructor(new Type[] { }))));
                list.Add(TransformerHelper.BuildBody(data.Value(), value, MembersOrder1, MembersOrder2));
                list.Add(Expression.Label(Expression.Label(data.Type), data));
            }

            return Expression.Lambda<Func<T, IData>>(TransformerHelper.IsEqualsTypes(Type1, Type2) ? list[0] : Expression.Block(typeof(IData), new ParameterExpression[] { data }, list), value);
        }

        public Expression<Func<IData, T>> CreateFromMethod()
        {
            var idata = Expression.Parameter(typeof(IData));
            var data = Expression.Variable(typeof(Data<>).MakeGenericType(Type2));
            var value = Expression.Variable(typeof(T));

            List<Expression> list = new List<Expression>();

            if (TransformerHelper.IsEqualsTypes(Type1, Type2))
                list.Add(Expression.Label(Expression.Label(Type1), Expression.Convert(idata, data.Type).Value()));
            else
            {
                list.Add(Expression.Assign(data, Expression.Convert(idata, data.Type)));
                list.Add(TransformerHelper.BuildBody(value, data.Value(), MembersOrder1, MembersOrder2));
                list.Add(Expression.Label(Expression.Label(Type1), value));
            }

            return Expression.Lambda<Func<IData, T>>(TransformerHelper.IsEqualsTypes(Type1, Type2) ? list[0] : Expression.Block(Type1, new ParameterExpression[] { data, value }, list), idata);
        }


        public IData To(T value1)
        {
            return to(value1);
        }

        public T From(IData value2)
        {
            return from(value2);
        }
    }

}
